/**
* Copyright (c) 2004-2013 Luciano Xumerle. All rights reserved.
*
* This file is part of gephinfo.
*
* gephinfo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
**/

package it.ciano.util;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.io.BufferedReader;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

/**
 * A static class with methods related to manage Strings.
 *
 * @author Luciano Xumerle
 * @version 0.0.1
 */
final public class CString {
    /**
     * The method check if a string is not null or length &gt; 0.
     *
     * @param str input string
     * @return true or false
     */
    public static boolean isOK ( String str ) {
        return ( str != null && str.trim().length() > 0 );
    }

    /**
     * The method check if a string is null or length == 0.
     *
     * @param str input string
     * @return true or false
     */
    public static boolean isEmpty ( String str ) {
        return ( !isOK ( str ) );
    }

    /**
     * The method change set to upper case the first character.
     * Example: "fooBar" becomes "FooBar"
     *
     * @param s input String
     * @return result string
     */
    public static String ucFirst ( String s ) {
        return s.substring ( 0, 1 ).toUpperCase() + s.substring ( 1 );
    }

    /**
     * Removes white space from both ends of this string and replaces multiple whitespaces with a single space.
     *
     * @param tt the input string.
     * @return trimmed string.
     */
    public static String trim ( String tt ) {
        StringBuffer buf = new StringBuffer();
        char[] ch = tt.trim().toCharArray();
        int index = 0;
        while ( index < ch.length ) {
            if ( Character.isWhitespace ( ch[ index ] ) ) {
                while ( Character.isWhitespace ( ch[ index ] ) )
                    index++;
                buf.append ( ' ' );
            }
            buf.append ( ch[ index++ ] );
        }
        return buf.toString();
    }

    /**
     * The method capitalize the input string.
     * Example: "fooBar" becomes "Foobar"
     *
     * @param strn the input string
     * @return capitalized string
     */
    public static String capitalize ( String strn ) {
        StringBuffer buf = new StringBuffer();
        char[] ch = strn.toCharArray();
        buf.append ( Character.toUpperCase ( ch[ 0 ] ) );
        for ( int i = 1; i < ch.length; ++i ) {
            if ( !Character.isLetter ( ch[ i - 1 ] ) )
                buf.append ( Character.toUpperCase ( ch[ i ] ) );
            else
                buf.append ( Character.toLowerCase ( ch[ i ] ) );
        }
        return buf.toString();
    }

    /**
     * The method converts a camel case string to a spaced string.
     * Example: "camelCaseVersusC" becomes "camel case versus c".
     *
     * @param s input String
     * @return results string
     */
    public static String addSpaces ( String s ) {
        StringBuffer buf = new StringBuffer();
        char[] ch = s.toCharArray ();
        for ( int i = 0; i < ch.length; ++i ) {
            if ( Character.isUpperCase ( ch[ i ] ) ) {
                buf.append ( ' ' );
                buf.append ( Character.toLowerCase ( ch[ i ] ) );
            } else
                buf.append ( ch[ i ] );
        }
        return buf.toString ();
    }

    /**
     * Removes spaces from a string and replaces first Letters with Capitals.
     * Example: "foo bar" becomes "FooBar"
     * @param s input String
     * @return the result string
     */
    public static String delSpaces ( String s ) {
        return replace ( capitalize ( s ).trim(), " ", "" );
    }

    /**
     * Adds spaces to left, right or left+rigth (default is rigth).
     *
     * @param s inpup String.
     * @param destLength destination string length.
     * @param type pad type [l,r,c] (left, right, center).
     * @param trim s is truncated if true and (s.length() &gt;= destLength).
     * @return the padded string.
     */
    public static String pad ( String s, int destLength, String type, boolean trim ) {
        StringBuffer res = new StringBuffer();
        if ( s == null || destLength < 1 )
            res.append ( "" );
        else
            res.append ( s );
        if ( s.length() >= destLength && !trim )
            return s;
        if ( type == null )
            type = "";
        if ( type.equals ( "l" ) ) {
            while ( res.length() < destLength ) {
                StringBuffer r = new StringBuffer();
                r.append ( ' ' ).append ( res );
                res = r;
            }
        } else if ( type.equals ( "c" ) ) {
            while ( res.length() < destLength ) {
                StringBuffer r = new StringBuffer();
                r.append ( ' ' ).append ( res ).append ( ' ' );
                res = r;
            }
        } else {
            while ( res.length() < destLength )
                res.append ( ' ' );
        }
        return res.toString().substring ( 0, destLength );
    }

    /**
     * Pads the String s to the given length by inserting blanks at the left side.
     * If s is longer than len and trim is set to true, the result is
     * truncated to the given length.
     *
     * @param       s String to pad.
     * @param       len length of resulting String.
     * @param       trim truncates s if longer then len and trim==true.
     * @return      the padded String.
     */
    static public String padLeft ( String s, int len, boolean trim ) {
        return pad ( s, len, "l", trim );
    }

    /**
     * Pads the String s to the given length by inserting blanks at the right end.
     * If s is longer than len and trim is true, the result is
     * truncated to the given length.
     *
     * @param       s String to pad.
     * @param       len length of resulting String.
     * @param       trim truncates s if longer then len and trim==true.
     * @return      the padded String.
     */
    static public String padRight ( String s, int len, boolean trim ) {
        return pad ( s, len, "r", trim );
    }


    /**
     * Concats String with itself n time.
     *
     * @param s input String.
     * @param n number of times.
     * @return concatenated String.
     */
    public static String concatString ( String s, int n ) {
        String res = s;
        for ( int i = 1; i < n; i++ )
            res += s;
        return res;
    }

    /**
     * The method replaces a string with another into an input string.
     *
     * @param input input string
     * @param src a substring of input string
     * @param dest the new destination substring
     * @return the string with the replaced substring
     */
    public static String replace ( String input, String src, String dest ) {
        int endIndex = input.indexOf ( src );
        if ( endIndex == -1 )
            return ( input );

        int startIndex = 0;
        StringBuffer preString = new StringBuffer ( input.length() );

        while ( true ) {
            // Copy original string to first occurance of src
            preString.append ( input.substring ( startIndex, endIndex ) );
            // Insert the replacement string
            preString.append ( dest );
            startIndex = endIndex + src.length();
            // Search for more occurances
            endIndex = input.indexOf ( src, startIndex );

            if ( endIndex == -1 ) {
                preString.append ( input.substring ( startIndex ) );
                return new String ( preString.toString() );
            }
        }
    }

    /**
     * The method splits a string using a given token.
     *
     * @param input the input string
     * @param token the token
     * @return the splitted array string
     */
    public static String[] split ( String input, String token ) {
        CList tt = new CList();
        // add one false element
        String in = input + token + "ee";
        int start = 0;
        int end = 0;
        int ll = in.length();
        while ( end > -1 && start < ll && end < ll ) {
            end = in.indexOf ( token, start );
            if ( end > -1 )
                tt.add ( in.substring ( start, end ) );
            else
                tt.add ( in.substring ( start ) );
            start = end + 1;
        }
        // skip last element (the false element)
        String res[] = new String[ tt.last() ];
        for ( int i = 0; i < tt.last(); i++ )
            res[ i ] = ( String ) tt.get ( i );
        return res;
    }

    /**
     * The method joins an String array.
     *
     * @param array input strings array
     * @param separator a string
     * @return joined string
     */
    public static String join ( String[] array, String separator ) {
        return join ( array, separator, 1, 0 ); // start > end => normal join
    }

    /**
     * The method joins an String array.
     * If start index or end index are out of range
     * then the method joins every array string.
     *
     * @param array input strings array
     * @param separator a string
     * @param start join array starting from this index
     * @param end last elemento to join into array
     * @return joined string
     */
    public static String join ( String[] array, String separator, int start, int end ) {
        if ( array==null || array.length==0 )
            return "";
        StringBuffer res = new StringBuffer();
        if ( separator == null )
            separator = "";
        if ( start > end || end > array.length || start < 0 ) {
            start = 0;
            end = array.length;
        }
        res.append ( array[ start ] );
        for ( int i = start + 1; i < end; i++ ) {
            res.append ( separator );
            res.append ( array[ i ] );
        }
        return res.toString();
    }

    /**
     * The method return a compiled regex RE
     *
     * @param regex the input string
     * @param caseInsensitive the match is case insensitive when true
     * @return compiled object RE
     */
    public static Pattern getRegex ( String regex, boolean caseInsensitive ) {
        Pattern p = null;
        if ( caseInsensitive )
            p = Pattern.compile ( regex, Pattern.CASE_INSENSITIVE );
        else
            p = Pattern.compile ( regex );
        return p;
    }

    /**
     * The method check if the input string match with a regex.
     *
     * @param in the input string
     * @param regex regular expression
     * @param caseInsensitive is true if match isa case insensitive
     * @return true or false
     */
    public static boolean match ( String in, String regex, boolean caseInsensitive ) {
        return getRegex ( regex, caseInsensitive ).matcher ( in ).find();
    }

    /**
     * The method replaces a substring using regular expression.
     *
     * @param in input string
     * @param regex regular expression to match
     * @param replace destination substring
     * @param caseInsensitive true if regex is case insensitive
     * @param global true to replace every matched substring
     * @return  result string
     */
    public static String replace ( String in, String regex, String replace, boolean caseInsensitive, boolean global ) {
        Matcher m = getRegex ( regex, caseInsensitive ).matcher ( in );
        if ( global )
            return m.replaceAll ( replace );
        else
            return m.replaceFirst ( replace );
    }

    /**
     * Prints out a String to standard output (default).
     *
     * @param a input String.
     * @param ok if true then prints the string.
     * @param stderr if true then prints the String to Standart Error.
     */
    public static void printDebug ( String a, boolean ok, boolean stderr ) {
        if ( ok ) {
            if ( stderr )
                System.err.println ( a );
            else
                System.out.println ( a );
        }
    }

    /**
     * The method gets an array where Strings match with the given regex.
     *
     * @param array a String array
     * @param regex String regex
     * @param inverseMatch if true method selects string not matched with regex
     * @param caseInsensitive if true method match case insensitive
     * @return the resulted string array or null if no strings match.
     */
    public static String[] grep ( String[] array, String regex, boolean inverseMatch, boolean caseInsensitive ) {
        Pattern r = getRegex ( regex, caseInsensitive );
        CList indici = new CList();
        boolean doWork;
        for ( int i = 0; i < array.length; i++ ) {
            doWork = r.matcher ( array[ i ] ).find();
            if ( ( doWork && !inverseMatch ) || ( inverseMatch && !doWork ) )
                indici.add ( new Integer ( i ) );
        }
        if ( indici.size() > 0 ) {
            String res[] = new String[ indici.size() ];
            for ( int i = 0; i < indici.size(); i++ )
                res[ i ] = array[ ( ( Integer ) indici.get ( i ) ).intValue() ];
        }
        return null;
    }

    /**
     * The methods decode %hh (hex chars) chars into the URL to normal chars.
     *
     * @param s input string
     * @return the result string
     */
    public static String decode ( String s ) {
        StringBuffer sb = new StringBuffer();
        for ( int i = 0; i < s.length(); i++ ) {
            char c = s.charAt ( i );
            switch ( c ) {
                /* removed + check by me
                case '+':
                    sb.append( ' ' );
                    break;*/
            case '%':
                try {
                    if ( ( i + 3 ) <= s.length() )
                        sb.append ( ( char ) Integer.parseInt ( s.substring ( i + 1, i + 3 ), 16 ) );
                    i += 2;
                } catch ( NumberFormatException e ) {
                    sb.append ( '%' );
                }
                break;
            default:
                sb.append ( c );
                break;
            }
        }
        // Undo conversion to external encoding
        String result = sb.toString();
        try {
            byte[] inputBytes = result.getBytes ( "8859_1" );
            result = new String ( inputBytes );
        } catch ( UnsupportedEncodingException e ) {
            // The system should always have 8859_1
        }
        return result;
    }

    /**
     * The method checks if input string is an Integer.
     *
     * @param s input string
     * @return true or false
     */
    public static boolean isInteger ( String s ) {
        return ( getInteger ( s ) != null );
    }

    /**
     * Returns true if input String is an Integer number.
     *
     * @param s input string
     * @return the Integer number. Returns null if false.
     */
    public static Integer getInteger ( String s ) {
        try {
            return new Integer ( s );
        } catch ( NumberFormatException e ) {
            return null;
        }
    }

    /**
     * Returns the integer value from the given String.
     *
     * @param s input string.
     * @param min the min value of valid integer.
     * @param max the max value of valid integer.
     * @return the int value (returns the min value if string is not an integer value).
     */
    public static int getInteger ( String s, int min, int max ) {
        Integer ii = getInteger ( s );
        if ( ii == null )
            return min;
        int yy = ii.intValue();
        if ( yy > max || yy < min )
            return min;
        return yy;
    }

    /**
     * Returns true if input String is a Float number.
     *
     * @param s input string
     * @return true or false
     */
    public static boolean isFloat ( String s ) {
        return ( getFloat ( s ) != null );
    }

    /**
      * Converts the String and returns the Float if possible.
      * The method detects positive and negative float numbers with
      * dot (Eg. 2.54) or comma (Eg. -5,76).
      *
      * @param num String with a Float number.
      * @return the number. null if a NumberFormatException occours
      */
    public static Float getFloat ( String num ) {
        try {
            int dot = num.indexOf ( "," );
            if ( dot > -1 ) {
                StringBuffer tt = new StringBuffer();
                tt.append ( num.substring ( 0, dot ) );
                tt.append ( "." );
                tt.append ( num.substring ( dot + 1, num.length() ) );
                num = tt.toString();
            }
            return new Float ( num );
        } catch ( NumberFormatException e ) {
            return null;
        }
    }

    /**
     * The method gets a string from STDIN.
     *
     * @return string captured from STDIN
     */
    public static String getInput() {
        try {
            BufferedReader in = new BufferedReader ( new InputStreamReader ( System.in ) );
            return in.readLine().trim() ;
        } catch ( IOException e ) {
            return "";
        }
    }

} // END CLASS

